﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace V32VM
{
    public abstract class Source
    {
        public abstract uint GetValue();
        public Source Flatten() { return new ConstantSource(this.GetValue()); }

        public static Source Parse(string str)
        {
            string[] parts = str.Split((char[])null, StringSplitOptions.RemoveEmptyEntries);
            Stack<Source> stack = new Stack<Source>();

            foreach (string part in parts)
            {
                // try to parse constant
                try { stack.Push(new ConstantSource(uint.Parse(part))); continue; }
                catch { }

                // try to parse register
                try { stack.Push(new RegisterSource(part)); continue; }
                catch { }
                // try to parse watch name
                bool foundWatch = false;
                foreach (Watch watch in Watch.Watches)
                {
                    if (watch.Name == part)
                    {
                        stack.Push(new WatchSource(part));
                        foundWatch = true;
                        break;
                    }
                }
                if (foundWatch) continue;

                // operators: +, -, @
                if (part == "+" || part == "-")
                {
                    if (stack.Count < 2)
                        throw new Exception("'" + part + "' requires two operands.");
                    Source op2 = stack.Pop();
                    Source op1 = stack.Pop();
                    stack.Push(new OffsetSource(op1, op2, part == "+"));
                    continue;
                }
                if (part == "@")
                {
                    if (stack.Count < 1)
                        throw new Exception("'@' requires one operand.");
                    stack.Push(new IndirectSource(stack.Pop()));
                    continue;
                }
                else if (part.ToLower() == "@b")
                {
                    if (stack.Count < 1)
                        throw new Exception("'@b' requires one operand.");
                    stack.Push(new IndirectSource(stack.Pop(), false));
                    continue;
                }
                else if (part.ToLower() == "@w")
                {
                    if (stack.Count < 1)
                        throw new Exception("'@w' requires one operand.");
                    stack.Push(new IndirectSource(stack.Pop(), true));
                    continue;
                }

                throw new Exception("Could not parse '" + part + "' as a constant, register, watch name, or operator.");
            }

            if (stack.Count != 1)
                throw new Exception("There must be one resultant source.");
            return stack.Pop();
        }
        public abstract string ToParsableString();

        public bool ContainsWatch(Watch watch)
        {
            if (this is ConstantSource || this is RegisterSource) return false;
            else if (this is OffsetSource)
            {
                OffsetSource offset = this as OffsetSource;
                return offset.Base.ContainsWatch(watch) || offset.Offset.ContainsWatch(watch);
            }
            else if (this is IndirectSource)
            {
                IndirectSource indirection = this as IndirectSource;
                return indirection.Source.ContainsWatch(watch);
            }
            else // this is WatchSource
                return (this as WatchSource).Name == watch.Name;
        }
        public bool IsRecursive(Watch[] watches)
        {
            return isRecursive(watches, new List<string>());
        }
        private bool isRecursive(Watch[] watches, List<string> watchesSeen)
        {
            if (this is ConstantSource || this is RegisterSource) return false;
            else if (this is OffsetSource)
            {
                OffsetSource offset = this as OffsetSource;
                return offset.Base.isRecursive(watches, watchesSeen)
                    || offset.Offset.isRecursive(watches, watchesSeen);
            }
            else if (this is IndirectSource)
            {
                IndirectSource indirection = this as IndirectSource;
                return indirection.Source.isRecursive(watches, watchesSeen);
            }
            else // this is WatchSource
            {
                WatchSource watchSrc = this as WatchSource;
                if (watchesSeen.Contains(watchSrc.Name)) return true;
                watchesSeen.Add(watchSrc.Name);

                Watch watch = (from w in watches where w.Name == watchSrc.Name select w).FirstOrDefault();
                return watch.Source.isRecursive(watches, watchesSeen);
            }
        }
    }

    public class ConstantSource : Source
    {
        private uint Value { get; set; }
        public ConstantSource(uint value) { Value = value; }
        public override uint GetValue() { return Value; }
        public override string ToString() { return Value.ToString(); }
        public override string ToParsableString() { return Value.ToString(); }
    }
    public class RegisterSource : Source
    {
        private uint RegIndex { get; set; }
        public RegisterSource(string regName) { RegIndex = Registers.FromString(regName); }
        public override uint GetValue() { return Program.VM.getReg(RegIndex); }
        public override string ToString() { return Registers.ToString(RegIndex); }
        public override string ToParsableString() { return Registers.ToString(RegIndex); }
    }
    public class WatchSource : Source
    {
        public string Name { get; private set; }
        public WatchSource(string watchName) { Name = watchName; }
        public override uint GetValue()
        {
            foreach (Watch watch in Watch.Watches)
                if (watch.Name == Name)
                    return watch.GetValue();
            throw new Exception("No watch with name '" + Name + "'.");
        }
        public override string ToString() { return Name; }
        public override string ToParsableString() { return Name; }
    }
    public class OffsetSource : Source
    {
        public Source Base { get; private set; }
        public Source Offset { get; private set; }
        private bool Plus { get; set; }
        public OffsetSource(Source source, Source offset, bool plus = true)
        { Base = source; Offset = offset; Plus = plus; }
        public override uint GetValue()
        { return Plus ? Base.GetValue() + Offset.GetValue() : Base.GetValue() - Offset.GetValue(); }
        public override string ToString()
        { return Base.ToString() + (Plus ? "+" : "-") + Offset.ToString(); }
        public override string ToParsableString()
        { return Base.ToParsableString() + " " + Offset.ToString() + " " + (Plus ? "+" : "-"); }
    }
    public class IndirectSource : Source
    {
        public Source Source { get; private set; }
        private bool? Word { get; set; }
        public IndirectSource(Source source, bool? word = null) { Source = source; Word = word; }
        public override uint GetValue()
        {
            if (!Word.HasValue || Word.Value)
                return Program.VM.memory.words[Program.VM.regs.ip, Source.GetValue()];
            return Program.VM.memory[Program.VM.regs.ip, Source.GetValue()];
        }
        public override string ToString() { return "[" + Source.ToString() + "]" + (Word.HasValue ? (Word.Value ? "w" : "b") : ""); }
        public override string ToParsableString() { return Source.ToParsableString() + " @" + (Word.HasValue ? (Word.Value ? "w" : "b") : ""); }
    }
}
